package pkg

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/mitchellh/mapstructure"
	"github.com/philchia/agollo/v4"
	"github.com/spf13/viper"
	"reflect"
	"strconv"
	"strings"
	"time"
)

type ConfigPkg struct {
	*viper.Viper
	ConfigPath     string // 本地配置文件路径
	ConfigType     string // 本地配置文件类型
	LocalStructMap map[string]interface{}
}

type ApolloPkg struct {
	*viper.Viper
	apClients  map[string]agollo.Client // apollo客户端实例map
	apAliasMap map[string]string        // 使用viper读写apollo配置时的外部调用简写key关联
	prefix     string
}

type ApolloConfPkg struct {
	Service  string                  `mapstructure:"service"`
	CacheDir string                  `mapstructure:"cache_dir"`
	App      map[string]AppConfigPkg `mapstructure:"app"`
}

type AppConfigPkg struct {
	AppID      string   `mapstructure:"app_id"`
	Cluster    string   `mapstructure:"cluster"`
	Secret     string   `mapstructure:"secret"`
	Namespaces []string `mapstructure:"namespaces"`
}

// ApUpdateCallback 外部指定的apollo配置变更后的回调，用于外部跟进处理配置变更后的后续逻辑
type ApUpdateCallback func(appId string, namespace string, changeKeys []string)

var Config = &ConfigPkg{}

var Apollo = &ApolloPkg{Viper: viper.New()}

// Init 配置初始化
func (c *ConfigPkg) Init(configPath string, configType string) {
	c.ConfigPath = configPath
	c.ConfigType = configType
}

// Read 读取本地配置文件
func (c *ConfigPkg) Read(file string) *ConfigPkg {
	vp := &ConfigPkg{Viper: viper.New()}
	vp.AddConfigPath(c.ConfigPath)
	vp.SetConfigType(c.ConfigType)
	vp.SetConfigName(file)
	if err := vp.ReadInConfig(); err != nil {
		panic(fmt.Sprintf("config read error：%s", err))
	}
	return vp
}

// Update 动态写入本地配置文件
func (c *ConfigPkg) Update(updates map[string]interface{}, prefix string) error {
	for key, value := range updates {
		fullKey := key
		if len(prefix) > 0 {
			fullKey = prefix + "." + key
		}
		c.Set(fullKey, value)
	}
	if err := c.WriteConfigAs(c.ConfigFileUsed()); err != nil {
		return fmt.Errorf("failed to write config: %w", err)
	}
	return nil
}

func (c *ConfigPkg) GetStringMapStruct(key string, outputStruct interface{}) error {
	mp := c.Viper.GetStringMap(key)
	return decode(mp, &outputStruct)
}

// Start 启动apollo配置中心，配置读写依赖viper
func (c *ApolloPkg) Start(apConf *ApolloConfPkg, callback ApUpdateCallback) error {
	c.apClients = make(map[string]agollo.Client, len(apConf.App))
	c.apAliasMap = make(map[string]string)

	for _, conf := range apConf.App {
		localConf := conf

		namespaces := localConf.Namespaces

		// 创建一个新的切片用于存储分割后的右侧部分
		realNamespaces := make([]string, 0, len(namespaces))

		// 遍历切片中的每个元素
		for _, namespace := range namespaces {
			parts := strings.SplitN(namespace, ":", 2)
			if len(parts) == 2 {
				// 将分割后的右侧部分添加到新的切片中
				realNamespaces = append(realNamespaces, parts[1])
				c.apAliasMap[parts[0]] = fmt.Sprintf("%s.%s", localConf.AppID, parts[1])
			} else {
				realNamespaces = append(realNamespaces, namespace)
			}
		}

		client := agollo.NewClient(&agollo.Conf{
			AppID:           localConf.AppID,
			Cluster:         localConf.Cluster,
			CacheDir:        apConf.CacheDir,
			AccesskeySecret: localConf.Secret,
			NameSpaceNames:  realNamespaces,
			MetaAddr:        apConf.Service,
		})

		err := client.Start()
		if err != nil {
			return fmt.Errorf("failed to start apollo: %w", err)
		}

		// 配置更新监听，支持apollo的 properties 和 json
		client.OnUpdate(func(event *agollo.ChangeEvent) {
			var changeKeys []string
			for _, change := range event.Changes {
				changeKeys = append(changeKeys, change.Key)
			}

			updateConfigs := c.transApolloConfigFormat2Map(client, event.Namespace)
			if len(updateConfigs) > 0 {
				_ = c.Update(map[string]interface{}{
					localConf.AppID + "." + event.Namespace: updateConfigs,
				})
			}

			// 内部的OnUpdate监听仅负责对服务端变更配置的更新，其余处理走外部回调
			if callback != nil {
				callback(localConf.AppID, event.Namespace, changeKeys)
			}
		})
		c.apClients[localConf.AppID] = client

		updateMap := make(map[string]interface{})
		for _, namespace := range realNamespaces {
			subMap := c.transApolloConfigFormat2Map(client, namespace)
			updateMap[conf.AppID+"."+namespace] = subMap
		}
		_ = c.Update(updateMap)
	}

	return nil
}

func (c *ApolloPkg) Alias(alias string) *ApolloPkg {
	conf := &ApolloPkg{
		Viper: c.Viper,
	}
	if apKey, ok := Apollo.apAliasMap[alias]; ok {
		conf.prefix = apKey
	}
	return conf
}

func (c *ApolloPkg) Struct(alias string, outputStruct interface{}) error {
	key := ""
	if strings.Contains(alias, ".") {
		parts := strings.SplitN(alias, ".", 2)
		alias = parts[0]
		key = parts[1]
	}
	conf := c.Alias(alias)
	mp := conf.GetStringMap(key)
	return decode(mp, &outputStruct)
}

// Update 合并方式更新
func (c *ApolloPkg) Update(updates map[string]interface{}) error {
	configBytes, err := json.Marshal(updates)
	if err != nil {
		return fmt.Errorf("failed to update config of client(JSON marshal failed): %w", err)
	}
	c.SetConfigType("json")
	err = c.MergeConfig(bytes.NewBuffer(configBytes))
	return fmt.Errorf("failed to merge config of client: %w", err)
}

// GetString 重写viper的配置获取方法，兼容键名前缀
func (c *ApolloPkg) GetString(key string) string {
	return c.Viper.GetString(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetBool(key string) bool {
	return c.Viper.GetBool(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetInt(key string) int {
	return c.Viper.GetInt(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetInt32(key string) int32 {
	return c.Viper.GetInt32(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetInt64(key string) int64 {
	return c.Viper.GetInt64(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetUint(key string) uint {
	return c.Viper.GetUint(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetUint16(key string) uint16 {
	return c.Viper.GetUint16(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetUint32(key string) uint32 {
	return c.Viper.GetUint32(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetUint64(key string) uint64 {
	return c.Viper.GetUint64(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetFloat64(key string) float64 {
	return c.Viper.GetFloat64(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetTime(key string) time.Time {
	return c.Viper.GetTime(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetDuration(key string) time.Duration {
	return c.Viper.GetDuration(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetIntSlice(key string) []int {
	return c.Viper.GetIntSlice(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetStringSlice(key string) []string {
	return c.Viper.GetStringSlice(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetStringMap(key string) map[string]any {
	return c.Viper.GetStringMap(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetStringMapStruct(key string, outputStruct interface{}) error {
	mp := c.Viper.GetStringMap(c.getFinalApKey(key))
	return decode(mp, &outputStruct)
}

func (c *ApolloPkg) GetStringMapString(key string) map[string]string {
	return c.Viper.GetStringMapString(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetStringMapStringSlice(key string) map[string][]string {
	return c.Viper.GetStringMapStringSlice(c.getFinalApKey(key))
}

func (c *ApolloPkg) GetSizeInBytes(key string) uint {
	return c.Viper.GetSizeInBytes(c.getFinalApKey(key))
}

func (c *ApolloPkg) getFinalApKey(key string) string {
	// 如果配置文件没有根据格式配置每个namespace的前缀，则返回原生的key
	if c.prefix == "" {
		return key
	}
	// 如果键为空，则直接返回组装后的前缀作为key名，指向整个配置对象
	if key == "" {
		return c.prefix
	}
	return fmt.Sprintf("%s.%s", c.prefix, key)
}

// transApolloConfigFormat2Map 将apollo的properties、json配置格式转换为map[string]interface{}
func (c *ApolloPkg) transApolloConfigFormat2Map(client agollo.Client, namespace string) map[string]interface{} {
	subMap := make(map[string]interface{})
	namespaceOpt := agollo.WithNamespace(namespace)
	keys := client.GetAllKeys(namespaceOpt)
	for _, key := range keys {
		value := client.GetString(key, namespaceOpt)
		if strings.HasSuffix(namespace, ".json") {
			var data map[string]interface{}
			err := json.Unmarshal([]byte(value), &data)
			if err != nil {
				panic(err)
			}
			for subKey, subValue := range data {
				subMap[subKey] = subValue
			}
			break
		}
		var tmp []interface{}
		if err := json.Unmarshal([]byte(value), &tmp); err == nil {
			subMap[key] = tmp
		} else {
			subMap[key] = value
		}
	}
	return subMap
}

func decode(input map[string]interface{}, output interface{}) error {
	decoderConfig := &mapstructure.DecoderConfig{
		DecodeHook: mapstructure.ComposeDecodeHookFunc(
			mapstructure.StringToSliceHookFunc(","),
			stringToVariousTypesHook(),
		),
		Result: output,
	}

	decoder, err := mapstructure.NewDecoder(decoderConfig)
	if err != nil {
		return fmt.Errorf("map to struct decoder create error during getting config: %w", err)
	}

	if err = decoder.Decode(input); err != nil {
		return fmt.Errorf("decoder decoding error during getting config: %w", err)
	}

	return nil
}

// stringToVariousTypesHook 处理字符串到多种类型的转换
func stringToVariousTypesHook() mapstructure.DecodeHookFuncType {
	return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
		if f.Kind() != reflect.String {
			return data, nil
		}
		value, ok := data.(string)
		if !ok {
			return data, nil
		}

		// 根据目标类型进行转换
		switch t.Kind() {
		case reflect.String:
			return value, nil
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			result, err := strconv.ParseInt(value, 10, t.Bits())
			if err != nil {
				return nil, fmt.Errorf("failed to parse int during getting config: %w", err)
			}
			return result, nil
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			result, err := strconv.ParseUint(value, 10, t.Bits())
			if err != nil {
				return nil, fmt.Errorf("failed to parse uint during getting config: %w", err)
			}
			return result, nil
		case reflect.Bool:
			result, err := strconv.ParseBool(value)
			if err != nil {
				return nil, fmt.Errorf("failed to parse bool during getting config: %w", err)
			}
			return result, nil
		case reflect.Float32, reflect.Float64:
			floatValue, err := strconv.ParseFloat(value, t.Bits())
			if err != nil {
				return nil, fmt.Errorf("failed to parse float during getting config: %w", err)
			}
			if t.Kind() == reflect.Float32 {
				return float32(floatValue), nil
			}
			return floatValue, nil
		case reflect.Slice:
			var result []string
			if err := json.Unmarshal([]byte(value), &result); err != nil {
				return nil, fmt.Errorf("failed to unmarshal JSON during getting config with []string: %w", err)
			}
			return result, nil
		default:
			return nil, fmt.Errorf("unsupported JSON data parsing %s", t.Kind())
		}
	}
}
